<?php 
namespace common\services;
use common\models\Words;
use yii\base\ErrorException;

class WordsCheck
{
    protected $tree = null;
    protected $callIsNumeric = true;
    protected $words_array = [];
    /**
     * 非法词汇列表，一个非法词汇占用一行
     */
    public function __construct( )
    {
        $this->tree = new WordsNode();
        $data = Words::words_array();
        foreach($data as $word) {
            $word = trim(str_replace(['　',"\n\r", "\n", "\r"],  [' ' ], $word));
            //把空格符用vrvrbkvrrv代替，后面再替换成(\s+)表达式
            $word = preg_replace('/(\s+)/','vrvrbkvrrv', $word);
            if ( $word == '' ) {
                continue;
            }  
            //这里过转义正则表达式的特殊字符
            $this->words_array[] = preg_quote(strtolower($word) ,'/');           
            $this->setTree( strtolower($word) );
        }
        //去掉重复的
        if(!empty($this->words_array)) {
            $this->words_array = array_unique($this->words_array );
        }
        
         
    }
    protected function setTree($word)
    {
        $array = $this->strToArr($word);
        $tree = $this->tree;
        $l = count($array) - 1;
        foreach ($array as $k => $item) {
            $tree = $tree->getChildAlways($item);
            if ($l == $k) {
                $tree->end = true;
            }
        }
    }
    /**
     * @todo 敏感词过滤，返回结果
     * @param array $list  定义敏感词一维数组
     * @param string $string 要过滤的内容
     * @return [] $log 处理结果
     */
    function preg_check(  $string)
    {
       //先压缩字符串
       $string = trim(str_replace(['　',"\n\r", "\n", "\r"], [' '],  strtolower($string)));
       $patternList = []; 
       //分多次匹配       
       $page = 0;
       $pagesize = 500;
       $total = count($this->words_array);
       $pagetotal = ceil($total/ $pagesize);
       do {
          if ($total > 0) {
            $words = [];
            $i = $page * $pagesize;
            for($i; $i<= ($page +1 ) * $pagesize ; $i++){
                if(isset($this->words_array[$i])){
                    //还原敏感词里面的vrvrbkvrrv 用表达式(\s+)，并在敏感期的首位加上(\s+)
                $words[] = "(\s+)". str_replace('vrvrbkvrrv','(\s+)', $this->words_array[$i]) ."(\s+)";
                }            
            }
            //把当前敏感词数组，组合成一个长的正则表达式"/(\s+)nabo(\s+)ho(\s+)|(\s+)very(\s+)/i"
            //主要是处理敏感词能匹配有空格的而不是单词
            //例如两个敏感词 nabo , ho 能匹配到 Trade withi nabo ut on ho ur. 但是 Trade withinabout on hour.可以通过
            $pattern = "/".implode("|",$words)."/i";
            
            if (preg_match_all($pattern, " ". $string ." ", $matches)) {
                $patternList = array_merge($patternList, $matches[0]);
            }
          }
          $page = $page +1;

       }while( $page < $pagetotal);
     
      return $patternList;
     
    }
    /**
     * 先采用正则匹配看能否匹配到，匹配不到采用单词匹配再匹配一次;
     */
    public function check($str)
    {
      //新进行正则匹配
       $ret = []; 
       try{
          $ret =  $this->preg_check($str);
          if(empty($ret)){
          //再进行单词匹配
            $ret = $this->tree_check($str);
          }
       }catch(ErrorException $e){

          $ret = $this->tree_check($str);
       }
       
      
       return $ret;
    }
    /**
     * 返回包含的非法词汇
     * @param string $str
     * @return array
     */
    public function tree_check($str)
    {
        //先压缩字符串
        $str = trim(str_replace([' ','　', "\n", "\r"], ['' ], 
                  strtolower($str)));
        $ret = [];     

        loop: 
        $strLen = strlen($str);
        if ($strLen === 0) {
            return array_unique($ret);
        }       
         //挨个字符进行判断
        $tree = $this->tree;       
        $words = '';
        for ($i = 0; $i < $strLen; $i++) {
            //unicode范围 --> ord 范围
            //一字节 0-127 --> 0 - 127
            //二字节 128-2047 --> 194 - 223
            //三字节 2048-65535 --> 224 - 239
            //四字节 65536-1114111 --> 240 - 244 
            $ord = ord($str[$i]);
            if ($ord <= 127) {
                $word = $str[$i];
            } elseif ($ord <= 223) {
                $word = $str[$i] . $str[$i + 1];
                $i += 1;
            } elseif ($ord <= 239) {
                $word = $str[$i] . $str[$i + 1] . $str[$i + 2];
                $i += 2;
            } elseif ($ord <= 244) {
                //四字节
                $word = $str[$i] . $str[$i + 1] . $str[$i + 2] . $str[$i + 3];
                $i += 3;
            } else {
                //五字节php都溢出了
                //Parse error: Invalid UTF-8 codepoint escape sequence: Codepoint too large
                continue;
            }
            //判断当前字符                
            $tree = $tree->getChild($word);          
            if (is_null($tree)) {              
                //当前字不存在，则截取后再次循环
                $str =  substr($str, $i +1 ); 
                goto loop;
            } else {                
                $words .= $word;
                if ($tree->end) { 
                    $ret[] = $words; 
                    $words = "";
                    $tree = $this->tree;
                }
            }
        }
      
       return array_unique($ret);
    }
    protected function strToArr($str)
    {
        $array = [];
        $strLen = mb_strlen($str);
        for ($i = 0; $i < $strLen; $i++) {
            $array[] = mb_substr($str, $i, 1, "utf8");
        }
        return $array;
    }
}